package block

import (
	"fmt"
	"bytes"
	"encoding/gob"
	"log"
	"crypto/sha256"
	"encoding/hex"
	"crypto/ecdsa"
	"crypto/rand"
	"crypto/elliptic"
	"math/big"

	"blockchain.com/part_5/wallet"
	"strings"
)

// ---------------交易------------

const (
/*
	奖励金：即是挖到新块的奖励金。比特币中实际是没有存储这个数字。而是基于区块总数进行计算而得：
区块总数除以210000就是Subsidy。挖出创世块的奖励是50BTC，每挖出210000个块后，奖励减半。在我们
的实现中，这个奖励值将会是一个常量。
*/
	Subsidy = 10
)

// 交易:代表一个比特币交易
type Transaction struct {
	ID 		[]byte// 交易ID
	Vin		[]TXInput// 交易的输入
	Vout    []TXOutput// 交易的输出
}

// 检查transaction是否是coinbase交易
func (tx Transaction)IsCoinbase()bool  {
	return len(tx.Vin) == 1&&len(tx.Vin[0].Txid) == 0&&tx.Vin[0].Vout == -1
}

//// 设置交易ID
//func (tx *Transaction)SetID()  {
//	var encoded bytes.Buffer
//	var hash [32]byte
//
//	enc := gob.NewEncoder(&encoded)
//	err := enc.Encode(tx)
//	if err != nil {
//		log.Panic(err)
//	}
//
//	hash = sha256.Sum256(encoded.Bytes())
//
//	tx.ID = hash[:]
//}

/*
	修改：七、	创建新交易
	如果想要发送给其他人一些币。为止，我们需要创建一笔新的交易，将它放到块里面。然后挖出这个块。
现在需要实现实现一种通用的平台交易。
 */
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	wallets,err := wallet.NewWallets()
	if err != nil {
		log.Panic(err)
	}

	wallet1 := wallets.GetWallet(from)
	pubKeyHash := wallet.HashPubKey(wallet1.PublicKey)

	// (1)在创建新的输出前，需要找到未花费输出，并且确保它们有足够的价值。
	acc,validOutputs := bc.FindSpendableOutputs(pubKeyHash,amount)
	if acc < amount {
		log.Panic("ERROR:Not enough funds")
	}

	// 创建输入列表
	for txid,outs := range validOutputs{
/*
	encoding/hex包：hex包实现了16进制字符表示的编解码。

func DecodeString(s string) ([]byte, error)
返回hex编码的字符串s代表的数据。
*/
		txID,err := hex.DecodeString(txid)
		if err != nil {
			log.Panic(err)
		}

		for _,out := range outs{
			input := TXInput{txID,out,nil,wallet1.PublicKey}
			inputs = append(inputs,input)
		}
	}

	// (2)创建输出列表
	outputs = append(outputs,*NewTXOutput(amount,to))
	if acc > amount {
		outputs = append(outputs,*NewTXOutput(acc-amount,from))// 改变输出
	}

	tx := Transaction{nil,inputs,outputs}
	tx.ID = tx.Hash()
	bc.SignTransaction(&tx,wallet1.PrivateKey)

	return &tx
}

///*
//定义输入的锁定和解锁方法
//
//在这里，我们只是将script字段与unlockingData进行了比较。在后续文章我们基于私钥实现了地址以后，会对这部分进行改进。
//*/
//func (in *TXInput)CanUnlockedWith(unlockingData string) bool{
//	return in.ScriptSig == unlockingData
//}
//
//// （2）定义输出的锁定和解锁方法
//func (out *TXOutput)CanBeUnlockedWith(unlockingData string) bool {
//	return out.ScriptPubKey == unlockingData
//}

/*
	修改：创建一个coinbase交易

coinbase交易只有一个输出，没有输入。在我们的实现中，表现为Txid为空，Vout为-1.在当前实现中，
coinbase交易也没有在scriptSig中存储脚本，而只是存储了一个任意的字符串data。
*/
func NewCoinbaseTX(to,data string) *Transaction  {
	if data == "" {
/*
func Sprintf(format string, a ...interface{}) string
Sprintf根据format参数生成格式化的字符串并返回该字符串。
*/
		data = fmt.Sprintf("Reward to %s",to)
	}

	txin := TXInput{
		[]byte{},
		-1,
		nil,
		[]byte(data)}
	txout := NewTXOutput(Subsidy,to)
	tx := Transaction{
		nil,
		[]TXInput{txin},
		[]TXOutput{*txout}}
	tx.ID = tx.Hash()

	return &tx
}

/*
	五、（1）实现签名
签名以下数据：
(1)存储在已解锁输出的公钥哈希。它识别了一笔交易的“发送方”。
(2)存储在新的锁定输出里面的公钥哈希。它识别了一笔交易的“接收方”。
(3)新的输出值。

@privKey	私钥
@prevTXs	之前交易的map
*/
func (tx *Transaction)Sign(privKey ecdsa.PrivateKey,prevTXs map[string]Transaction)  {
	// 检测是否是一个coinbase交易，如果是不进行签名操作，因为coinbase交易没有实际输入，所以不需要被签名
	if tx.IsCoinbase() {
		return
	}

	// 遍历交易的输入
	for _,vin := range tx.Vin{
		if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
			log.Panic("ERROR:Previous transaction is not correct.")
		}
	}

	txCopy := tx.TrimmedCopy()

	// 迭代副本中每一个输入
	for inID,vin := range txCopy.Vin {
		prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil// 设置nil，是一个双重校验
		txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash// 设置成所引用输出的PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].PubKey = nil// 获取完哈希，我们应该重置PubKey字段，以便不会影响后面的迭代
	/*
		func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)
	使用私钥对任意长度的hash值（必须是较大信息的hash结果）进行签名，返回签名结果（一对大整数）。
	私钥的安全性取决于密码读取器的熵度（随机程度）。
	*/
		r,s,err := ecdsa.Sign(rand.Reader,&privKey,txCopy.ID)// 通过私钥privKey对txCopy进行签名。
		if err != nil {
			log.Panic(err)
		}

// 通过privKey对txCopy.ID进行签名。一个ECDSA签名就是一对数字，我们对这对数字连接起来，并存储在输入的Sigature 字段里面
		signature := append(r.Bytes(),s.Bytes()...)
		tx.Vin[inID].Signature = signature
	}
}

// 将交易进行hash处理：对交易进行序列化，并使用sha-256算法
func (tx *Transaction)Hash()[]byte  {
	var hash [32]byte

	txCopy := *tx
	txCopy.ID = []byte{}

	hash = sha256.Sum256(txCopy.Serialize())

	return hash[:]
}

// 将交易序列化
func (tx *Transaction)Serialize()[]byte  {
	var encoded	bytes.Buffer

	enc := gob.NewEncoder(&encoded)
	err := enc.Encode(tx)
	if err != nil {
		 log.Panic(err)
	}

	return encoded.Bytes()
}

/*
	五、（2）修剪交易
 不是一个完整的交易，这个副本包含了所有的输入和输出,但是TXInput.Signature和TXInput.PubKey设置为nil
*/
func (tx *Transaction)TrimmedCopy() Transaction {
	var inputs []TXInput
	var ouputs []TXOutput

	for _,vin := range tx.Vin{
		inputs = append(inputs,TXInput{vin.Txid,vin.Vout,nil,nil})
	}

	for _,vout := range tx.Vout {
		ouputs = append(ouputs,TXOutput{vout.Value,vout.PubKeyHash})
	}

	txCopy := Transaction{tx.ID,inputs,ouputs}

	return txCopy
}

// 五、（3）验证：验证交易输入的签名
func (tx *Transaction)Verify(prevTXs map[string]Transaction)bool  {
	// 先验证是否是coinbase价交易
	if tx.IsCoinbase() {
		return true
	}

	for _,vin := range tx.Vin{
		if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
			log.Panic("ERROR:Previous transaction is not correct.")
		}
	}

	txCopy := tx.TrimmedCopy()// 修剪交易:同一笔交易的副本
	curve := elliptic.P256()// 获取P-256曲线

	// 遍历交易输入
	for inID,vin := range txCopy.Vin{
		//此部分同Sign方法类似
		prevTX := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil
		txCopy.Vin[inID].PubKey = prevTX.Vout[vin.Vout].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].PubKey = nil// 获取完哈希，我们应该重置PubKey字段，以便不会影响后面的迭代

/*
	此部分是解包存储在TXIput.Signature和TXInput.PubKey中的值，因为一个签名就是一对数字，一个公钥就是一对
坐标。之前存储将它们连接在一起，现在需要对它们进行解包在crypto/ecdsa函数中使用。
*/
		r := big.Int{}
		s := big.Int{}
		sigLen := len(vin.Signature)
		r.SetBytes(vin.Signature[:sigLen/2])
		s.SetBytes(vin.Signature[sigLen/2:])

		x := big.Int{}
		y := big.Int{}
		keyLen := len(vin.PubKey)
		x.SetBytes(vin.PubKey[:keyLen/2])
		y.SetBytes(vin.PubKey[keyLen/2:])
/*
	(1)type PublicKey struct {
    	elliptic.Curve
    	X, Y *big.Int
	}
	PrivateKey代表一个ECDSA公钥。

	(2)func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
	使用公钥验证hash值和两个大整数r、s构成的签名，并返回签名是否合法。

	(3)使用输入提取的公钥创建一个ecdsa.PublicKey公钥，通过传入中提取的签名执行ecdsa.Verify验证。
	如果所有输入被验证，返回true,如果有任何一个验证失败，返回false。
*/
		rawPubKey := ecdsa.PublicKey{curve,&x,&y}
		if ecdsa.Verify(&rawPubKey,txCopy.ID,&r,&s) == false{
			return false
		}
	}

	return true
}

// 可读性的交易信息
func (tx Transaction)String()string  {
	var lines []string

	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID))

	for i,input := range tx.Vin {
		lines = append(lines, fmt.Sprintf("     Input %d:", i))
		lines = append(lines, fmt.Sprintf("       TXID:      %x", input.Txid))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Vout))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PubKey))
	}

	for i,output := range tx.Vout {
		lines = append(lines, fmt.Sprintf("     Output %d:", i))
		lines = append(lines, fmt.Sprintf("       Value:  %d", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %x", output.PubKeyHash))
	}

	return strings.Join(lines,"\n")
}